#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cassert>
#include <unordered_map>
#include "../commen/util.hpp"
#include "../commen/log.hpp"
#include "../commen/mysql.hpp"

//这个版本是MySQL + 本地文件
//数据库存储题目元信息，本地文件存储用户头部代码和测试用例与题目描述字段
namespace ns_oj_model
{
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_mysql;
    
    const std::string CONF_PATH = "./questions/questions.list";
    const std::string QUESTION_PATH = "./questions/";

    struct Question
    {
        std::string number; //题目编号，唯一
        std::string title;  //题目的标题
        std::string star;   //难度: 简单 中等 困难
        int cpu_limit;      //题目的时间要求(S)
        int mem_limit;      //题目的空间要去(KB)
        std::string desc;   //题目的描述
        std::string header; //题目预设给用户在线编辑器的代码
        std::string tail;   //题目的测试用例，需要和header拼接，形成完整代码

        Question()
        : number("")
        , title("")
        , star("")
        , cpu_limit(0)
        , mem_limit(0)
        , desc("")
        , header("")
        , tail("")
        {}

        Question(const std::string &number, const std::string &title, const std::string &star, int cpu_limit, int mem_limit)
        : number(number)
        , title(title)
        , star(star)
        , cpu_limit(cpu_limit)
        , mem_limit(mem_limit)
        , desc("")
        , header("")
        , tail("")
        {
            std::string desc_path = QUESTION_PATH + number + "/" + "desc.txt";
            std::string header_path = QUESTION_PATH + number + "/" + "header.hpp";
            std::string tail_path = QUESTION_PATH + number + "/" + "tail.cpp";
            ns_util::FileUtil::ReadFile(desc_path, desc, true);
            ns_util::FileUtil::ReadFile(header_path, header, true);
            ns_util::FileUtil::ReadFile(tail_path, tail, true);
        }

        friend std::ostream& operator<<(std::ostream& os, const Question& q)
        {
            os << q.number << " " << q.title << " " << q.star << " " << q.cpu_limit << " " << q.mem_limit << std::endl;
            os << q.desc << std::endl;
            os << q.header << std::endl;
            os << q.tail << std::endl;
            return os;
        }

        std::string Print()
        {
            std::string log = "number: " + number + "\n";
            log += "title: " + title + "\n";
            log += "star: " + star + "\n";
            log += "cpu_limit: " + std::to_string(cpu_limit) + "\n";
            log += "mem_limit: " + std::to_string(mem_limit) + "\n";
            return log;
        }
    };  

    class OjModel
    {
    private:
        std::unordered_map<std::string, Question> _questions;
        MYSQL* _mysql;

        void LoadConfToTable()
        {
            std::ifstream in(CONF_PATH);
            if (!in.is_open())
            {
                return;
            }
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> vec;
                ns_util::StringUtil::SplitString(line, vec, " ");
                if (vec.size() != 5)
                {
                    continue;
                }
                std::string number = vec[0];
                std::string title = vec[1];
                std::string star = vec[2];
                int cpu_limit = atoi(vec[3].c_str());
                int mem_limit = atoi(vec[4].c_str());
                std::string desc_path = QUESTION_PATH + number + "/" + "desc.txt";
                std::string header_path = QUESTION_PATH + number + "/" + "header.cpp";
                std::string tail_path = QUESTION_PATH + number + "/" + "tail.cpp";
                if (!ns_util::FileUtil::IsFileExist(desc_path) || !ns_util::FileUtil::IsFileExist(header_path) || !ns_util::FileUtil::IsFileExist(tail_path))
                {
                    LogMessage(Warning, "题目%d配置文件缺失，请检查\n", number);
                    continue;
                }
                Question q(number, title, star, cpu_limit, mem_limit);
                InsertProblem(q);
            }
            in.close();
        }
    public:
        OjModelSql()
        : _mysql(nullptr)
        {
            Init();
            LoadQuestions();
        }

        ~OjModelSql()
        {
            mysql_close(_mysql);
        }

        void Init()
        {
           assert(MySQLInit(_mysql, HOST, USER, PASSWD, DBNAME));
        }
        
        //从数据库中加载题目
        bool LoadQuestions()
        {
            LogMessage(Debug, "加载题目...\n");
            std::string table_name = "Questions";
            std::string sql = "select * from " + table_name;
            mysql_query(_mysql, sql.c_str());
            MYSQL_RES* res = mysql_store_result(_mysql);
            if (res == nullptr)
            {
                LogMessage(Error, "加载题目失败\n");
                return false;
            }
            int rows = mysql_num_rows(res);
            if (rows == 0)
            {
                LogMessage(Error, "加载题目失败, 没有题目\n");
                return false;
            }
            MYSQL_ROW row;
            while (row = mysql_fetch_row(res))
            {
                std::string number = row[0];
                std::string title = row[1];
                std::string star = row[2];
                int cpu_limit = atoi(row[3]);
                int mem_limit = atoi(row[4]);
                Question q(number, title, star, cpu_limit, mem_limit);
                _questions[number] = q;
            }
            mysql_free_result(res);
            LogMessage(Debug, "加载题目完成\n");
            return true;
        }

        //从配置文件中加载题目
        void LoadConf(std::string &conf_path)
        {
            std::ifstream in(conf_path);
            if (!in.is_open())
            {
                return;
            }
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> vec;
                ns_util::StringUtil::SplitString(line, vec, " ");
                if (vec.size() != 5)
                {
                    continue;
                }
                std::string number = vec[0];
                std::string title = vec[1];
                std::string star = vec[2];
                int cpu_limit = atoi(vec[3].c_str());
                int mem_limit = atoi(vec[4].c_str());
                Question q(number, title, star, cpu_limit, mem_limit);
                _questions[number] = q;
            }
            in.close();
        }

        bool InsertProblem(Question &q)
        {
            std::string table_name = "Questions";
            std::string sql = "insert into " + table_name + " values(" + q.number + ", '" + q.title + "', '" + q.star + "', " + std::to_string(q.cpu_limit) + ", " + std::to_string(q.mem_limit) + ", '" + q.desc + "', '" + q.header + "', '" + q.tail + "')";
            mysql_query(_mysql, sql.c_str());
            return true;
        }

        //获取指定题目
        bool GetOneQuestion(const std::string &number, Question *q)
        {
            auto it = _questions.find(number);
            if (it == _questions.end())
            {
                LogMessage(Error, "获取题目%s失败\n", number.c_str());
                q = nullptr;
                return false;
            }
            *q = it->second;
            return true;
        }

        //获取所有题目
        bool GetAllQuestions(std::vector<Question> *out)
        {
            for (auto &q : _questions)
            {
                out->push_back(q.second);
            }
            return true;
        }

        void ShowQuestions()
        {
            for (auto &q : _questions)
            {
                LogMessage(Debug, "%s\n", q.second.Print().c_str());
            }
        }
    };
}